package com.example.mykotlin.base.auth

import cn.dev33.satoken.secure.BCrypt
import com.example.mykotlin.base.util.tryOnly
import com.example.mykotlin.mapper.ApiPermissionMapper
import com.example.mykotlin.mapper.RoleMapper
import com.example.mykotlin.model.entity.ApiPermission
import com.example.mykotlin.model.entity.Role
import com.example.mykotlin.model.entity.User
import com.example.mykotlin.model.entity.UserRole
import com.example.mykotlin.model.enums.SystemRole
import com.mybatisflex.kotlin.extensions.db.filter
import com.mybatisflex.kotlin.extensions.db.insert
import com.mybatisflex.kotlin.extensions.db.mapper
import com.mybatisflex.kotlin.extensions.kproperty.eq
import com.mybatisflex.kotlin.extensions.kproperty.`in`
import io.swagger.v3.oas.annotations.Operation
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.time.LocalDateTime
import org.apache.commons.lang3.StringUtils
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.ApplicationArguments
import org.springframework.boot.ApplicationRunner
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware
import org.springframework.stereotype.Component
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

@Component
class InitAuth : ApplicationContextAware, ApplicationRunner {

    @Value("\${sa-token.auth-super-admin-user-name:poiuy}")
    private var systemAdminUserNames: String = ""

    @Value("\${sa-token.auth-init-super-admin-user:poiuy}")
    private var systemAdminInitUser: String = ""

    companion object {
        private val SystemAdminUserId: MutableSet<Long> = HashSet()

        fun isSystemAdminUser(id: Long) = SystemAdminUserId.contains(id)

    }

    private var applicationContext: ApplicationContext? = null
    override fun setApplicationContext(applicationContext: ApplicationContext) {
        this.applicationContext = applicationContext
    }

    override fun run(args: ApplicationArguments?) {
        initAdminUserCacheInfo()
        initApiPermission()
        initSystemRole()
    }

    private fun initAdminUserCacheInfo() {
        tryOnly {
            val idList = filter<User>(User::id) {
                User::username `in` systemAdminUserNames.split(",".toRegex()).map { it.trim() }
            }.map {
                it.id ?: 0
            }
            if (idList.isEmpty()) {
                val superAdminList = filter<UserRole> {
                    UserRole::roleId eq SystemRole.SUPER_ADMIN.name
                }
                if (superAdminList.isEmpty()) {
                    val defaultAdminUserId = 1L
                    val decryptPassword = BCrypt.hashpw(systemAdminInitUser)
                    val defaultAdminUser = User()
                    defaultAdminUser.id = defaultAdminUserId
                    defaultAdminUser.username = systemAdminInitUser
                    defaultAdminUser.password = decryptPassword
                    defaultAdminUser.nickname = systemAdminInitUser
                    defaultAdminUser.avatar = systemAdminInitUser
                    defaultAdminUser.createTime = LocalDateTime.now()
                    if (insert(defaultAdminUser) > 0) SystemAdminUserId.add(defaultAdminUserId)
                }
            } else {
                SystemAdminUserId.addAll(idList)
            }
        }
    }

    private fun initApiPermission() {
        tryOnly {
            val handlerMapping =
                applicationContext?.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping::class.java)
            val handlerMethods = handlerMapping?.handlerMethods

            val list = ArrayList<ApiPermission>()
            handlerMethods?.forEach {
                if (it.value.beanType == BasicErrorController::class.java) return@forEach
                val url = it.key.pathPatternsCondition?.patterns?.toList()
                if (url.isNullOrEmpty()) return@forEach
                val apiPermission = ApiPermission()
                val uriPath = url[0].patternString
                apiPermission.uriPath = uriPath
                val method = it.key.methodsCondition.methods.toList()
                if (method.isNotEmpty()) apiPermission.method = method[0].name
                // val urlPathMethodMd5 = md5(uriPath + apiPermission.method)
                // apiPermission.id = urlPathMethodMd5
                // if (urlPathMethodMd5.length < 32) {
                //     val l = 32 - urlPathMethodMd5.length
                //     if (uriPath.length < l) apiPermission.id = urlPathMethodMd5 + uriPath
                //     else for (i in 1..l) {
                //         apiPermission.id += uriPath.elementAt(uriPath.length - i)
                //     }
                // }
                apiPermission.id =
                    (if (apiPermission.method == null) "ALL" else apiPermission.method) + uriPath.replace("/", "_")
                val annotation = it.value.method.getAnnotation(Operation::class.java)
                if (annotation == null) {
                    apiPermission.name = it.value.method.name
                    apiPermission.description = it.value.method.name
                } else {
                    apiPermission.name =
                        if (StringUtils.isEmpty(annotation.summary)) it.value.method.name else annotation.summary
                    apiPermission.description =
                        if (StringUtils.isEmpty(annotation.description)) it.value.method.name else annotation.description
                }
                list.add(apiPermission)
                if (list.size >= 20) {
                    tryOnly {
                        mapper<ApiPermissionMapper>().replaceIntoApiPermission(list)
                    }
                    list.clear()
                }
            }
            if (list.size > 0) {
                tryOnly {
                    mapper<ApiPermissionMapper>().replaceIntoApiPermission(list)
                }
            }
        }
    }

    private fun initSystemRole() {
        tryOnly {
            val roleEnumEntries = SystemRole.entries
            val systemRoleList = roleEnumEntries.map { it.name }
            val systemRoleInDbList = filter<Role>(Role::id) {
                (Role::id `in` systemRoleList)
            }
            val now = LocalDateTime.now()
            if (systemRoleInDbList.size < systemRoleList.size) {
                val roleInDbSet = systemRoleInDbList.map { it.id }.toHashSet()
                val needInsertSystemRole = roleEnumEntries.filter { !roleInDbSet.contains(it.name) }
                    .map { Role(it.name, it.desc(), it.desc(), now, now) }
                mapper<RoleMapper>().insertBatch(needInsertSystemRole)
            }
        }
    }

    private fun md5(value: String): String {
        try {
            val md5 = MessageDigest.getInstance("MD5")
            md5.update(value.toByteArray(StandardCharsets.UTF_8))
            val b = md5!!.digest()
            val sb = StringBuilder()
            for (i in b) {
                if (i in 33..126) {
                    sb.append(i.toInt().toChar())
                } else {
                    sb.append(Integer.toHexString(i + 128))
                }
            }
            return sb.toString()
        } catch (e: Exception) {
            return value.substring(0, if (value.length <= 32) value.length else 32)
        }
    }

}